import constants from '@/constants';
import { BaseService } from '@/core/base.service';
import { HuifuService } from '@/core/huifu.service';
import { SandService } from '@/core/sand.service';
import { UopService } from '@/core/uop.service';
import { YopService } from '@/core/yop.service';
import {
  PlanListDto,
  PlanLoadDto,
  PlanOrderDto,
  PlanQueryDto,
  PlanRpaImportDto,
  PlanRpaOrderDto,
} from '@/dtos';
import {
  BillStatusEnum,
  BillTypeEnum,
  OrderStatusEnum,
  OrderTypeEnum,
  PaymentTypeEnum,
  PlanStatusEnum,
  TokenLogTypeEnum,
  TokenSourceEnum,
  TokenStatusEnum,
} from '@/enums';
import { getCode, halt } from '@/utils';
import { InjectQueue } from '@nestjs/bull';
import { Injectable } from '@nestjs/common';
import { Plan, Prisma } from '@prisma/client';
import { Queue } from 'bull';
import dayjs from 'dayjs';
import Decimal from 'decimal.js';
import { SettingService } from './setting.service';
import getMAC from 'getmac';
import { sleep } from 'radash';

@Injectable()
export class PlanService extends BaseService {
  constructor(
    @InjectQueue('order')
    private readonly orderQueue: Queue,
    private readonly settingService: SettingService,
    private readonly uopService: UopService,
    private readonly yopService: YopService,
    private readonly huifuService: HuifuService,
    private readonly sandService: SandService,
  ) {
    super();
  }

  async getList({ params, sort }: PlanListDto) {
    const { current = 1, pageSize = 20, keyword, type, ...where } = params;

    let sql = `
      SELECT #{fields} FROM Plan AS t
      LEFT JOIN Asset AS a ON t.assetId = a.id
      LEFT JOIN Album AS aa ON aa.id = a.albumId
      LEFT JOIN Publisher AS p ON a.publisherId = p.id
      WHERE t.isDeleted = false
        AND t.isEnabled = true
         ${!!keyword ? `AND t.title LIKE '%${keyword}%'` : ''}
         ${!!type ? `AND aa.type = ${type}` : ''}
    `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Plan[]>(
      `${sql.replace(
        '#{fields}',
        `
        t.id, t.title, t.image, t.isSoldOut, t.sort, t.isEnabled, t.isUseWhiteList,
        t.startTime, t.endTime, t.price, t.limit, t.perLimit, t.userLimit, t.assetId,
        t.desc, t.type, p.name AS publisherName, p.logo AS publisherLogo
        `,
      )}
      ORDER BY ${
        !!sort && Object.keys(sort).length > 0
          ? Object.entries(sort)
              .map(([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`)
              .join(',')
          : 't.sort, t.createdAt DESC'
      }
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((o) => ({
        ...o,
        price: Number(o.price),
      })),
    };
  }

  async query({ params, sort }: PlanQueryDto) {
    const { current = 1, pageSize = 20, keyword, ...where } = params;

    let sql = `
      SELECT #{fields} FROM Plan AS t
      WHERE t.isDeleted = false
    `;

    const [{ count }] = await this.prisma.$queryRawUnsafe<[{ count: number }]>(
      sql.replace('#{fields}', 'COUNT(t.id) AS count'),
    );

    const list = await this.prisma.$queryRawUnsafe<Plan[]>(
      `${sql.replace(
        '#{fields}',
        `
        t.id, t.title, t.image, t.isSoldOut, t.sort, t.isEnabled, t.isUseWhiteList,
        t.startTime, t.endTime, t.tips, t.status, t.price, t.limit, t.perLimit, t.userLimit, t.assetId
        `,
      )}
      ORDER BY ${
        !!sort && Object.keys(sort).length > 0
          ? Object.entries(sort)
              .map(([k, v]) => `t.${k} ${v === 'descend' ? 'DESC' : 'ASC'}`)
              .join(',')
          : 't.createdAt DESC'
      }
      LIMIT ${pageSize} 
      OFFSET ${(current - 1) * pageSize}
      `,
    );

    const total = count ? Number(count) : 0;
    const totalPage = Math.ceil(total / pageSize);
    const hasMore = current < totalPage;

    return {
      total,
      totalPage,
      hasMore,
      current,
      pageSize,
      list: list.map((o) => ({
        ...o,
        price: Number(o.price),
      })),
    };
  }

  async findOne(
    where: Prisma.PlanWhereUniqueInput,
    select?: Prisma.PlanSelect,
  ): Promise<Plan | null> {
    return this.prisma.plan.findUnique({
      where,
      select,
    });
  }

  async create(data: Prisma.PlanCreateInput): Promise<Plan> {
    return this.prisma.plan.create({
      data: {
        ...data,
        ...(data.startTime
          ? { startTime: dayjs(data.startTime.toString()).toDate() }
          : {}),
        ...(data.endTime
          ? { endTime: dayjs(data.endTime.toString()).toDate() }
          : {}),
      },
    });
  }

  async update(params: {
    where: Prisma.PlanWhereUniqueInput;
    data: Prisma.PlanUpdateInput;
  }): Promise<Plan> {
    const { where, data } = params;
    return this.prisma.plan.update({
      where,
      data: {
        ...data,
        ...(data.startTime
          ? { startTime: dayjs(data.startTime.toString()).toDate() }
          : {}),
        ...(data.endTime
          ? { endTime: dayjs(data.endTime.toString()).toDate() }
          : {}),
      },
    });
  }

  async remove(where: Prisma.PlanWhereUniqueInput): Promise<Plan> {
    return this.prisma.plan.update({
      where,
      data: {
        isDeleted: true,
      },
    });
  }

  async order(
    { id, num }: PlanOrderDto,
    userId: number,
    ip: string,
    mobile?: string,
  ) {
    await this.util.wait(userId, ip);

    return await this.redlock.using([`user:${userId}`], 10000, {}, async () => {
      const { times, isWhiteList } = await this.getTimes(id, userId, mobile);

      if (num > times) {
        num = times;
      }

      if (num <= 0) {
        return halt('系统太火爆，请稍后重试');
      }

      const plan = await this.prisma.plan.findUnique({
        where: {
          id,
          isDeleted: false,
        },
      });
      if (!plan.isEnabled) {
        return halt('数据异常');
      }
      if (plan.isSoldOut) {
        return halt('发售已结束');
      }
      const currentTime = dayjs();
      if (currentTime.isBefore(dayjs(plan.startTime))) {
        return halt('活动尚未开始');
      }
      if (currentTime.isAfter(dayjs(plan.endTime))) {
        return halt('活动已经结束');
      }
      if (Number(num) > Number(plan.perLimit)) {
        return halt(`每次数量不能超过${plan.perLimit}`);
      }

      const user = await this.prisma.user.findUnique({
        where: {
          id: userId,
          isBlockd: false,
          isDeleted: false,
        },
      });
      if (!user) {
        return halt('用户信息异常');
      }
      if (!user.isReal) {
        return halt('请先完成实名认证');
      }

      if (
        !times ||
        isNaN(times) ||
        !num ||
        isNaN(num) ||
        times <= 0 ||
        times < num
      ) {
        console.log(`plan:times:${id}:${mobile}`, times, isWhiteList);
        return halt(plan.tips ?? '可用购买资格不足');
      }

      if (!isWhiteList) {
        const total = await this.getTotal(id);
        if (num > total) {
          return halt(plan.tips ?? `库存不足`);
        }
      }

      const count = await this.prisma.order.count({
        where: {
          ownerId: userId,
          status: OrderStatusEnum.PENDING,
          isDeleted: false,
        },
      });
      if (count > 0) {
        return halt('请先完成待支付订单');
      }
      return await this.redlock.using([`plan:${id}`], 3000, {}, async () => {
        return await this.prisma.$transaction(async (tx) => {
          const tokens = await tx.token.findMany({
            where: {
              assetId: plan.assetId,
              ownerId: constants.default.ownerId,
              status: TokenStatusEnum.HOLD,
              isDeleted: false,
            },
            select: {
              id: true,
              classId: true,
              no: true,
              price: true,
              sellerId: true,
              ownerId: true,
              owner: true,
            },
            take: num,
            orderBy: {
              hash: 'desc',
            },
          });
          if (tokens.length < num) {
            return halt(`库存不足`);
          }

          const createTime = dayjs();
          const expireTime = createTime.add(10, 'minutes');
          const total = num * Number(plan.price);
          const fee = 0;

          let no = null;
          let payNo = null;
          let payUrl = null;
          if (this.config.get('isUseBalance')) {
            if (total > Number(user.balance)) {
              return halt('余额不足');
            }
            no = await this.uopService.generateOrderNo();
          } else {
            if (this.config.get('payment') === PaymentTypeEnum.YOP) {
              no = await this.yopService.generateOrderNo();
              const res = await this.yopService.order({
                orderId: no,
                orderAmount: new Decimal(total).toDecimalPlaces(2).toNumber(),
                goodsName: `${plan.title}`,
                returnUrl: `${this.yopService.ReturnUrl}/mine`,
                notifyUrl: `${this.yopService.NotifyUrl}/api/order/callback`,
              });
              if (!res || !res.isSuccess || res?.result?.code !== '00000') {
                return halt(res?.result?.message ?? '易宝订单异常');
              }
              payNo = res.result.uniqueOrderNo;
              payUrl = res.result.cashierUrl;
            } else if (this.config.get('payment') === PaymentTypeEnum.HUIFU) {
              if (!user.userCustId || !user.acctId) {
                return halt('请先开通汇付钱包');
              }
              no = await this.huifuService.generateOrderNo();
              const res = await this.huifuService.pay033({
                order_date: dayjs().format('YYYYMMDD'),
                order_id: no,
                order_expire_time: dayjs(expireTime).format('YYYYMMDDHHmmss'),
                user_cust_id: user.userCustId,
                trans_amt: new Decimal(total).toFixed(2),
                goods_desc: `${plan.title}`,
                div_type: '0',
                div_details: JSON.stringify([
                  {
                    riskDivType: '1',
                    divCustId: '6666000151035758',
                    divAcctId: 'A34354090',
                    // divCustId: this.config.get('huifu.merCustId'),
                    // divAcctId: this.config.get('huifu.merAcctId'),
                    divAmt: new Decimal(total).toFixed(2),
                  },
                ]),
                dev_info_json: JSON.stringify({
                  devType: '2',
                  ipAddr: ip,
                  MAC: getMAC().replaceAll(':', ''),
                }),
                object_info: JSON.stringify({
                  objectName: `${plan.title}`,
                  marketType: 1,
                }),
              });
              if (!res || res?.resp_code !== 'C00000') {
                return halt(res?.resp_desc ?? '汇付订单异常');
              }
              payNo = res.order_id;
              payUrl = res.pay_url;
            } else if (this.config.get('payment') === PaymentTypeEnum.SNAD) {
              no = await this.sandService.generateOrderNo();
              const res = await this.sandService.recharge({
                orderNo: no,
                orderAmt: new Decimal(total).toDecimalPlaces(2).toString(),
                ip,
                returnUrl: `${this.sandService.ReturnUrl}/mine`,
                notifyUrl: `${this.sandService.NotifyUrl}/api/order/callback`,
                goodsName: `${plan.title}`,
                extra: JSON.stringify({}),
              });
              if (!res || !res.no || !res.url) {
                return halt('杉徳订单异常');
              }
              payNo = res.no;
              payUrl = res.url;
            } else {
              return halt('支付方式异常');
            }
          }

          const order = await tx.order.create({
            data: {
              no,
              num,
              total,
              fee,
              expireTime: expireTime.toDate(),
              planId: id,
              assetId: plan.assetId,
              ownerId: userId,
              payType: this.config.get('payment'),
              payNo,
              payUrl,
              type: OrderTypeEnum.INITIAL,
              status: OrderStatusEnum.PENDING,
              createdAt: createTime.toDate(),
            },
          });

          for (let t of tokens) {
            await tx.token.update({
              where: {
                id: t.id,
              },
              data: {
                status: TokenStatusEnum.LOCKING,
              },
            });
            await tx.subOrder.create({
              data: {
                no: await this.uopService.generateOrderNo(),
                price: Number(plan.price),
                fee: 0,
                orderId: order.id,
                assetId: id,
                tokenId: t.id,
                buyerId: user.id,
                sellerId: t.ownerId,
              },
            });
          }

          await this.orderQueue.add(
            'cancel',
            {
              no: order.no,
            },
            {
              delay: expireTime.diff(dayjs()),
            },
          );

          for (let t of tokens) {
            if (order.type === OrderTypeEnum.INITIAL) {
              await this.redis.lpush(`plan:total:${order.planId}`, t.id);
              await this.redis.lpush(
                `plan:user:${order.planId}:${mobile}`,
                t.id,
              );
            }
          }

          return { no: order.no };
        });
      });
    });
  }

  async load({ id, rows }: PlanLoadDto) {
    const plan = await this.prisma.plan.findUnique({
      where: {
        id,
      },
    });
    if (!plan) {
      return halt('目标不存在');
    }

    await this.util.cleanKeys(`plan:total:${id}:*`);
    await this.util.cleanKeys(`plan:user:${id}:*`);
    await this.util.cleanKeys(`plan:whitelist:${id}:*`);

    if (plan.isUseWhiteList) {
      for (let item of rows) {
        if (!!item.mobile) {
          await this.redis.set(
            `plan:whitelist:${plan.id}:${item.mobile}`,
            Number(item.num),
          );
        }
      }
    }

    await this.prisma.plan.update({
      where: {
        id,
      },
      data: {
        status: PlanStatusEnum.COMPLETED,
      },
    });

    return true;
  }

  async getTimes(
    id: number,
    userId: number,
    mobile?: string,
  ): Promise<{ times: number; isWhiteList?: boolean }> {
    // const user = await this.prisma.user.findUnique({
    //   where: {
    //     id: userId,
    //     isBlockd: false,
    //     isDeleted: false,
    //   },
    // });
    // if (!user) {
    //   return halt('用户信息异常');
    // }
    // if (!user.isReal) {
    //   return { times: 0 };
    // }

    const plan = await this.prisma.plan.findUnique({
      where: { id },
    });
    if (!plan) {
      return halt('数据异常');
    }
    if (!plan.isEnabled) {
      return { times: 0 };
    }
    if (plan.isSoldOut) {
      return { times: 0 };
    }
    const limit = await this.redis.llen(`plan:user:${id}:${mobile}`);
    console.log(`plan:user:${id}:${mobile}`, limit);
    if (plan.isUseBlackList) {
      const blacklist = await this.redis.get(`plan:blacklist:${id}:${mobile}`);
      console.log(`plan:blacklist:${id}:${mobile}`, blacklist);
      if (!blacklist || Number(blacklist) <= 0) {
        return { times: 0 };
      }
      return { times: Number(blacklist) - limit, isWhiteList: true };
    }
    if (plan.isUseWhiteList) {
      const whitelist = await this.redis.get(`plan:whitelist:${id}:${mobile}`);
      console.log(`plan:whitelist:${id}:${mobile}`, whitelist);
      if (!whitelist || Number(whitelist) <= 0) {
        return { times: 0 };
      }
      return { times: Number(whitelist) - limit, isWhiteList: true };
    }
    return { times: plan.userLimit - limit };
  }

  async getTotal(id: number) {
    const plan = await this.prisma.plan.findUnique({
      where: { id },
    });
    if (!plan) {
      return halt('数据异常');
    }
    const total = await this.redis.llen(`plan:total:${plan.id}`);
    return plan.totalLimit - Number(total);
  }

  async generateOrderNo() {
    return `${dayjs().format('YYYYMMDDHHmmss')}${await getCode(11)}`;
  }

  async rpaOrder({ id, rows }: PlanRpaOrderDto) {
    return await this.redlock.using([`plan:${id}`], 10000, {}, async () => {
      await sleep(2000);
      const plan = await this.prisma.plan.findUnique({
        where: {
          id,
        },
      });
      if (!plan) {
        return halt('数据异常');
      }
      for (let { mobile, num } of rows) {
        console.log(`RPA下单: ${mobile} - ${num}`);
        for (let i = 0; i < num; i++) {
          let count = 1;
          await this.prisma.$transaction(async (tx) => {
            const user = await tx.user.findUnique({
              where: {
                mobile,
                isBlockd: false,
                isDeleted: false,
              },
            });
            if (!user) {
              return halt(`用户${mobile}信息异常`);
            }
            if (!user.isReal) {
              return halt(`用户${mobile}未实名`);
            }
            console.log('余额', user.balance);
            const tokens = await tx.token.findMany({
              where: {
                assetId: plan.assetId,
                ownerId: constants.default.ownerId,
                status: TokenStatusEnum.HOLD,
                isDeleted: false,
              },
              select: {
                id: true,
                classId: true,
                no: true,
                price: true,
                sellerId: true,
                ownerId: true,
                owner: true,
              },
              take: count,
              orderBy: {
                hash: 'desc',
              },
            });
            if (tokens.length < count) {
              return halt(plan.tips ?? `库存不足`);
            }

            const createTime = dayjs();
            const expireTime = createTime.add(10, 'minutes');
            const total = count * Number(plan.price);
            const fee = 0;

            let no = null;
            let payNo = null;
            let payUrl = null;
            if (this.config.get('isUseBalance')) {
              if (total > Number(user.balance)) {
                return halt('余额不足');
              }
              no = await this.uopService.generateOrderNo();
            }

            const order = await tx.order.create({
              data: {
                no,
                num: count,
                total,
                fee,
                expireTime: expireTime.toDate(),
                planId: id,
                assetId: plan.assetId,
                ownerId: user.id,
                payType: this.config.get('payment'),
                payNo,
                payUrl,
                type: OrderTypeEnum.INITIAL,
                status: OrderStatusEnum.PENDING,
                createdAt: createTime.toDate(),
              },
            });

            for (let t of tokens) {
              await tx.token.update({
                where: {
                  id: t.id,
                },
                data: {
                  status: TokenStatusEnum.LOCKING,
                },
              });
              await tx.subOrder.create({
                data: {
                  no: await this.uopService.generateOrderNo(),
                  price: Number(plan.price),
                  fee: 0,
                  orderId: order.id,
                  assetId: id,
                  tokenId: t.id,
                  buyerId: user.id,
                  sellerId: t.ownerId,
                },
              });
            }

            if (!order) {
              return halt('订单信息异常');
            }

            if (total > Number(user.balance)) {
              return halt('余额不足');
            }

            const subOrders = await tx.subOrder.findMany({
              where: {
                orderId: order.id,
                buyerId: user.id,
              },
              select: {
                id: true,
                price: true,
                fee: true,
                token: {
                  select: {
                    id: true,
                    no: true,
                    price: true,
                    classId: true,
                    asset: true,
                  },
                },
                seller: {
                  select: {
                    id: true,
                    mobile: true,
                    account: true,
                    version: true,
                  },
                },
              },
            });
            for (let o of subOrders) {
              const amount = Number(o.price) - Number(o.fee);
              await tx.token.update({
                where: {
                  id: o.token.id,
                },
                data: {
                  bidPrice: Number(o.price),
                  bidAt: new Date(),
                  ownerId: user.id,
                  source: TokenSourceEnum.TRADE,
                  status: TokenStatusEnum.HOLD,
                },
              });
              const seller = await tx.user.findUnique({
                where: {
                  id: o.seller.id,
                },
              });
              await tx.user.update({
                where: {
                  id: seller.id,
                  version: seller.version,
                },
                data: {
                  version: {
                    increment: 1,
                  },
                  balance: {
                    increment: amount,
                  },
                },
              });
              await tx.tokenLog.create({
                data: {
                  log: `买入${o.price}￥`,
                  type: TokenLogTypeEnum.TRANSFER,
                  tokenId: o.token.id,
                  fromId: o.seller.id,
                  recipientId: user.id,
                },
              });
              await tx.bill.create({
                data: {
                  no: await this.generateOrderNo(),
                  total: amount,
                  fee: 0,
                  type: BillTypeEnum.SELL,
                  status: BillStatusEnum.COMPLETED,
                  ownerId: o.seller.id,
                  remark: `卖出《${o.token.asset.title}》#${o.token.no}`,
                },
              });
            }
            if (order.type === OrderTypeEnum.INITIAL) {
              // await tx.asset.update({
              //   where: {
              //     id: order.assetId,
              //   },
              //   data: {
              //     circulation: {
              //       increment: order.num,
              //     },
              //   },
              // });
            }
            await tx.user.update({
              where: {
                id: user.id,
                version: user.version,
              },
              data: {
                version: {
                  increment: 1,
                },
                balance: {
                  decrement: total,
                },
              },
            });
            await tx.bill.create({
              data: {
                no: await this.generateOrderNo(),
                total,
                fee: 0,
                type: BillTypeEnum.BUY,
                status: BillStatusEnum.COMPLETED,
                ownerId: user.id,
                orderId: order.id,
                remark: `支付订单: ${order.no}`,
              },
            });
            await tx.order.update({
              where: {
                id: order.id,
              },
              data: {
                payTime: new Date(),
                status: OrderStatusEnum.COMPLETED,
              },
            });
          });
        }
      }
    });
  }

  async rpaImport({ id, rows }: PlanRpaImportDto) {
    const plan = await this.prisma.plan.findUnique({
      where: {
        id,
      },
    });
    if (!plan) {
      return halt('目标不存在');
    }

    await this.util.cleanKeys(`plan:blacklist:${id}:*`);

    if (plan.isUseBlackList) {
      console.log('黑名单导入开始');
      for (let item of rows) {
        if (!!item.mobile) {
          console.log(
            `黑名单: ${plan.id} - ${item.mobile} - ${Number(item.num)}`,
          );
          await this.redis.set(
            `plan:blacklist:${plan.id}:${item.mobile}`,
            Number(item.num),
          );
        }
      }
      console.log('黑名单导入结束');
    }

    return true;
  }
}
